
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "ipcshm.h"

#if defined(WIN32) || defined(WIN64) || defined(WINCE) || defined(_MSC_VER)
/* ---------- Windows begin ---------- */
#include "windows.h"
#include "process.h"
#define SLEEPMS(ms) Sleep(ms)

void* ipcshm_create(const char* uniqueId, uint32_t memSize)
{
    PGShareMemBase* base = NULL;
    do
    {
        if (memSize < sizeof(PGShareMemBase))
        {
            printf("ipcshm_create failed: \"%s\" size/%d less then %ld \r\n", uniqueId, memSize, sizeof(PGShareMemBase));
            break;
        }

        /* id */
        void* id = CreateFileMapping(
            INVALID_HANDLE_VALUE,
            NULL,
            PAGE_READWRITE,
            0,
            memSize,
            uniqueId);
        if (!id) {
            printf("ipcshm_create CreateFileMapping failed: \"%s\" size/%d\r\n", uniqueId, memSize);
            break;
        }

        /* address */
        base = (PGShareMemBase*)MapViewOfFile(id, FILE_MAP_ALL_ACCESS, 0, 0, 0);
        if (!base) {
            printf("ipcshm_create MapViewOfFile failed: \"%s\" size/%d id/%p \r\n", uniqueId, memSize, id);
            CloseHandle(id);
            break;
        }

        base->size = memSize;
        base->id.p = id;
        base->isBusy = 0;

    } while(0);

    return (void*)base;
}

void ipcshm_release(void** _base)
{
    if (!_base) {
        return;
    }
    PGShareMemBase** base = (PGShareMemBase**)_base;
    UnmapViewOfFile((*base)->id.p);
    CloseHandle((*base)->id.p);
    *base = NULL;
}

void ipcshm_getuuid(char* buff, int32_t buffSize, const char* append)
{
    snprintf(buff, buffSize, "%d-%d-%s", GetCurrentProcessId(), clock(), append);
}
/* ---------- Windows end ---------- */
#else
/* ---------- Linux begin ---------- */
#include <unistd.h>
#include <sys/stat.h>
#include <sys/shm.h>
#define SLEEPMS(ms) usleep(ms * 1000)

static const uint8_t PGSHM_ROOT_PATH_ID = 1;

static key_t ipcshm_key(const char* uniqueId, char* tagPath, int32_t tagPathSize)
{
    /* 生成挂靠目录,检查和创建 */
    snprintf(tagPath, tagPathSize - 1, "/tmp/ipcshm-%s", uniqueId);
    if (access(tagPath, F_OK) != 0) {
        mkdir(tagPath, 0666);
    }
    /* 根据文件生成唯一key */
    return ftok(tagPath, PGSHM_ROOT_PATH_ID);
}

void* ipcshm_create(const char* uniqueId, uint32_t memSize)
{
    PGShareMemBase* base = NULL;
    char tagPath[128] = {0};
    do
    {
        if (memSize < sizeof(PGShareMemBase))
        {
            printf("ipcshm_create failed: \"%s\" size/%d less then %ld \r\n", uniqueId, memSize, sizeof(PGShareMemBase));
            break;
        }
        /* key */
        key_t key = ipcshm_key(uniqueId, tagPath, sizeof(tagPath));
        if (key < 0)
        {
            printf("ipcshm_create ipcshm_key failed(%d): \"%s\" size/%d \r\n", key, uniqueId, memSize);
            break;
        }
        /* id */
        int32_t isCreate = 0;
        int32_t id = shmget(key, memSize, 0666);
        if (id < 0)
        {
            /* 新建 */
            isCreate = 1;
            id = shmget(key, memSize, IPC_CREAT | 0666);
        }
        if (id < 0)
        {
            printf("ipcshm_create shmget failed(%d): \"%s\" size/%d \r\n", id, uniqueId, memSize);
            break;
        }
        /* 取内存 */
        base = (PGShareMemBase*)shmat(id, NULL, 0);
        if (!base)
        {
            printf("ipcshm_create shmat failed(%d): \"%s\" size/%d \r\n", id, uniqueId, memSize);
            break;
        }
        /* 第一个创建人,才有权限重置以下内容 */
        if (isCreate)
        {
            base->size = memSize;
            base->id.i = id;
            base->isBusy = 0;
            snprintf(base->tagPath, sizeof(base->tagPath), "%s", tagPath);
            base->userIndex = 0;
            memset(base->userPidList, 0, sizeof(base->userPidList));
        }
        /* 用户数+1 */
        uint32_t userIndex = base->userIndex++;
        /* 填入当前用户pid */
        base->userPidList[userIndex % (sizeof(base->userPidList) / sizeof(base->userPidList[0]))] = getpid();
    } while(0);

    if (!base && access(tagPath, F_OK) == 0) {
        rmdir(tagPath);
    }

    return (void*)base;
}

void ipcshm_release(void** _base)
{
    if (!_base) {
        return;
    }
    PGShareMemBase* base = *((PGShareMemBase**)_base);
    if (base)
    {
        /* 遍历检查所有用户在线状态 */
        int32_t onlineUserPid = 0;
        int32_t myPid = getpid();
        /* 通过检查 /proc/pid 文件夹来判断进程存在 */
        char procPath[128] = {0};
        for (int32_t i = 0; i < sizeof(base->userPidList) / sizeof(base->userPidList[0]); i++)
        {
            /* 清除自己的位置 */
            if (base->userPidList[i] == myPid)
            {
                base->userPidList[i] = 0;
            }
            else if (base->userPidList[i] > 0)
            {
                snprintf(procPath, sizeof(procPath), "/proc/%d", base->userPidList[i]);
                if (access(procPath, F_OK) == 0) {
                    onlineUserPid = base->userPidList[i];
                    break;
                }
            }
        }
        /* 无人在线,删除共享内存和挂靠路径 */
        if (onlineUserPid == 0)
        {
            rmdir(base->tagPath);
            shmctl(base->id.i, IPC_RMID, NULL);
        }
    }
    *_base = NULL;
}

void ipcshm_getuuid(char* buff, int32_t buffSize, const char* append)
{
    snprintf(buff, buffSize, "%d-%d-%s", getpid(), clock(), append);
}
/* ---------- Linux end ---------- */
#endif

void ipcshm_lock(void* _base, int32_t timeoutMs)
{
    PGShareMemBase* base = (PGShareMemBase*)_base;
    if (!base) {
        return;
    }
    while (base->isBusy && timeoutMs > 0) {
        SLEEPMS(2);
        timeoutMs -= 2;
    }
    base->isBusy = 1;
}

void ipcshm_unlock(void* _base)
{
    PGShareMemBase* base = (PGShareMemBase*)_base;
    if (!base) {
        return;
    }
    base->isBusy = 0;
}
